作者:陈广 日期:2018-3-11
上篇文章,我们简单了解了下Razor,大概知道了Razor是怎么回事。今天来做一个稍微复杂点的程序。这个例子源自ASP.NET Core文档中的一篇文章:ASP.NET Core中的Razor页面介绍。当然,我的风格是极简,把这个例子能简化的地方全部简化了。越简单,越容易入门,牵涉的东西太多,脑袋容易搞不清状况。学完我这篇文章,再去看微软的文章,会更容易理解一些。接下来跟我一起做例子。
dotnet new empty
Startup
类代码如下:public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc();
}
}
这些步骤和上一篇文章完全一样。
在之前写的HTTP协议使用这篇文章中,为了方便,我们把数据直接加到了Controller里面,当时访问数据时没有做任何页面。而这次的程序,不但有页面,还会有多个页面访问数据,所以必须要把数据独立出来。当然,现在还没有涉及到数据库,所以还是使用一个集合来代替数据库,程序关闭,数据消失。另外还需要针对增删改写专门的方法以方便页面调用。
在项目根目录下新建一个Data文件夹,并在Data文件夹下新建一个Student.cs文件。输入如下代码:
using System.Collections.Generic;
namespace RazorPage.Data
{
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
//无参构造函数必须写,否则Create页面无法使用 [BindProperty]
public Student() { }
}
public static class Students
{
public static List<Student> Stus { get; set; } = new List<Student>();
//添加一个学生
public static void Add(Student stu)
{
Stus.Add(stu);
}
//通过id获取相应的Student对象
public static Student GetById(int id)
{
for (int i = 0; i < Stus.Count; i++)
{
if (Stus[i].Id == id)
{
return Stus[i];
}
}
return null;
}
//删除一个学生,删除成功返回元素位置索引,失败返回-1
public static int Delete(int id)
{
for (int i = 0; i < Stus.Count; i++)
{
if (Stus[i].Id == id)
{
Stus.RemoveAt(i);
return i;
}
}
return -1;
}
//更改指定Id的学生的姓名,成功返回修改元素位置索引,失败返回-1
public static int Update(int id,string name)
{
for (int i = 0; i < Stus.Count; i++)
{
if (Stus[i].Id == id)
{
Stus[i].Name = name;
return i;
}
}
return -1;
}
}
}
此页面创建了一个Students类,用于存放多个Student的信息,并添加了增删改查方法,这些之后都要使用到。当然,为简单,代码并不健壮,比如未考虑Id相同的可能性。
主页面Index的功能有:浏览所有Student、到新建Student页面的链接、到编辑Student页面的链接、删除Student。
在项目根目录下新建一个Pages文件夹,然后在Pages文件夹下面新建一个Index.cshtml文件,再建一个Code-behind文件Index.cshtml.cs。
在Index.cshtml文件中输入如下代码:
@page
@model RazorPage.Pages.IndexModel
<h1>学生列表</h1>
<form method="post">
<table class="table">
<thead>
<tr>
<th>学号</th>
<th>姓名</th>
</tr>
</thead>
<tbody>
@foreach(var stu in Model.Stus)
{
<tr>
<td>@stu.Id</td>
<td>@stu.Name</td>
</tr>
}
</tbody>
</table>
</form>
在code-behind文件Index.cshtml.cs中输入如下代码:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Collections.Generic;
using RazorPage.Data;
namespace RazorPage.Pages
{
public class IndexModel : PageModel
{
public IList<Student> Stus { get; private set; }
public void OnGet()
{
//这两句添加数据的代码仅用于浏览测试
Students.Stus.Add(new Student() { Id = 1, Name = "张三" });
Students.Stus.Add(new Student() { Id = 2, Name = "李四" });
Stus = Students.Stus;
}
}
}
code-behind文件中的OnGet()
方法对就的就是HTTP中的Get请求,请参考HTTP协议使用,先看完它再来看本文。也就是说当浏览器发送Get请求时,会触发code-behind文件中的OnGet()
方法。OnGet()
方法相应的异步方法为OnGetAsync()
,这里由于网页是自己一个人学习使用,为了简单,只使用了同步方法,在实际开发中面对的是多用户操作,应尽量使用异步方法。
在OnGet()
方法方法中我们将Stus
属性指向之前创建的数据类。而Stus
也是code-behind文件和页面文件间的数据传输通道,也就是说页面文件通过code-behind文件的Stus
属性来获取数据。
在Stus
指向Students
之前,我们往Students
里放了两条数据,这只是为了页面有数据可显示,等下运行完程序这两句就可以删掉了。
运行程序,效果如下图所示:
在Index.cshtml文件中我们使用的是@foreach
指令来创建表格显示数据,非常方便。我们刷新页面,会发现张三、李四会不停地重复添加,这当然是不对的。所以接来下专门做一个页面用于添加数据。
在Pages文件夹下面新建一个Create.cshtml文件,再建一个Code-behind文件Create.cshtml.cs。
在Create.cshtml文件中输入如下代码:
@page
@model RazorPage.Pages.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<html>
<body>
<p>请输入新的学生信息</p>
<form method="POST">
<div>学号:<input asp-for="Stu.Id"/></div>
<div>姓名:<input asp-for="Stu.Name"/></div>
<input type="submit"/>
</form>
</body>
</html>
这里我们使用了表单,让用户输入数据,并在表单中使用标签助手asp-for
绑定code-behind文件的Stu
属性中的Id
和Name
属性。有关标签助手,微软写了非常详细的文档,我已翻译完毕,请参阅相关章节。
在Create.cshtml.cs文件中输入如下代码:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPage.Data;
namespace RazorPage.Pages
{
public class CreateModel : PageModel
{
[BindProperty]
public Student Stu { get; set; }
public IActionResult OnPost()
{
Students.Add(Stu);
return RedirectToPage("/Index");
}
}
}
这里声明了Stu
属性,它是code-behind文件一页面的交流通道,需要注意的是它使用了[BindProperty]
特性。[BindProperty]
使得一个属性可以实现双向绑定。这里我们要有一个概念,页面是在客户端(浏览器)上运行的,而code-behind文件是在服务器上运行的。从上篇文章中我们知道,在Razor页面中可以通过@
来访问code-behind文件中的属性值。在使用asp-for
标签助手将一个Input
绑定到这个属性时,我们还希望Input
中值的改变可以影响到服务器中的code-behind文件里的属性值,这时就需要给属性加上[BindProperty]
特性。当然,这种改变需要在表单提交之后才会传导到服务器。
当浏览器通过表单向服务器提交Post请求时,会触发OnPost()
方法,在这个方法里,表单的Post请求会更新Stu
的属性值,然后我们将更新后的对象加入到Students
内,从而实现了数据的添加。
最后,通过返回RedirectToPage("/Index")
,将页面重定向至Index页面,当然Index页面会重新获取服务器端的数据,从而将新加的数据显示出来。接下来需要在Index页面中添加一个指向Create页面的链接。
更改Index.cshtml文件如下:
@page
@model RazorPage.Pages.IndexModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<h1>学生列表</h1>
<form method="post">
<table class="table">
<thead>
<tr>
<th>学号</th>
<th>姓名</th>
</tr>
</thead>
<tbody>
@foreach(var stu in Model.Stus)
{
<tr>
<td>@stu.Id</td>
<td>@stu.Name</td>
</tr>
}
</tbody>
</table>
<a asp-page="./Create">新建</a>
</form>
这里我们在窗体下方创建了一个<a>
标签,并使用标签助手asp-page
将<a>
标签的链接指向了Create
页面。
运行程序,效果如下。
实现添加条目的功能后,有了数据,我们现在可以实现删除功能了。删除功能直接在Index页面上实现。
在Index.cshtml文件中,更改<tbody>
标签部分代码如下:
<tbody>
@foreach(var stu in Model.Stus)
{
<tr>
<td>@stu.Id</td>
<td>@stu.Name</td>
<td>
<button type="submit" asp-page-handler="delete"
asp-route-id="@stu.Id">删除</button>
</td>
</tr>
}
</tbody>
我们在每个条目的后面增加了一个删除按钮,并使用asp-page-handler
标签助手指明此按钮动作为删除动作,使用asp-route-id
标签助手来传递删除条目的Id值。
打开Index.cshtml.cs文件,在IndexModel
类中添加如下方法:
public IActionResult OnPostDelete(int id)
{
Students.Delete(id);
return RedirectToPage();
}
注意,我们这里删除数据并没有使用HTTP的Delete方法,而是使用Post方法进行删除,这是因为我们使用的是表单,它只有Post和Get。为表明此Post执行的是删除操作,在页面中使用了asp-page-handler="delete"
进行指明。使得在按下删除按钮之后,会触发code-behind文件中的OnPostDelete
方法。在此方法中传递了一个参数id
,通过此id
,我们可以删除相应条目。然后重定向回本页面。
运行程序,添加几个条目后,效果如下图所示:
在Pages文件夹下面新建一个Edit.cshtml文件,再建一个Code-behind文件Edit.cshtml.cs。
在Edit.cshtml文件中输入如下代码:
@page "{id:int}"
@model RazorPage.Pages.EditModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<h1>编辑学生-@Model.Stu.Id</h1>
<form method="POST">
<input asp-for="Stu.Id" type="hidden"/>
<div>
<input asp-for="Stu.Name"/>
</div>
<div>
<button type="submit">保存</button>
</div>
</form>
虽然本例的id不允许更改,但还是为Stu.id
设置了一个<input>
。这是因为Code-behind文件的Stu
属性设置了[BindProperty]
,它的两个字段都必须和<input>
连接,即使id不允许更改,折中的办法就是把id所对应的<input>
隐藏起来,这样就更改不了了。
在Edit.cshtml.cs文件中输入如下代码:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPage.Data;
namespace RazorPage.Pages
{
public class EditModel : PageModel
{
[BindProperty]
public Student Stu { get; set; }
public IActionResult OnGet(int id)
{
Student stu1 = Students.GetById(id);
if (stu1 == null)
{
return RedirectToPage("/Index");
}
Stu = new Student() { Id = stu1.Id, Name = stu1.Name };
return Page();
}
public IActionResult OnPost()
{ //更新数据
Students.Update(Stu.Id, Stu.Name);
return RedirectToPage("/Index");
}
}
}
在页面发送Get请求时,我们从Student克隆了相应id的Student并赋予Stu
属性。在页面发送Post请求更改数据时,再把Stu
内更新的数据重新返还给Student。这里没有办法直接对Strudents相应的Student直接更新。
接下来为每个条目添加一个编辑链接,打开Index.cshtml文件,更改<tbody>
代码如下:
<tbody>
@foreach(var stu in Model.Stus)
{
<tr>
<td>@stu.Id</td>
<td>@stu.Name</td>
<td>
<a asp-page="./Edit" asp-route-id="@stu.Id">编辑</a>
<button type="submit" asp-page-handler="delete"
asp-route-id="@stu.Id">删除</button>
</td>
</tr>
}
</tbody>
运行程序,添加几个条目后进行更改,效果如下:
上面写的程序,重新启动后之前数据都会消失,这让人多少有些不爽。但现在还不想涉及数据库,有什么办法可以永久保存数据呢?当然有,最方便的莫过于json了。现如今json大行其道,大部分的配置文件都使用json进行保存。试问json编程哪家强,当属json.net,速度最快,使用最方便。更让人高兴的是.NET Core好象已经集成了json.net。只需引入Newtonsoft.Json
命名空间就可以使用了。
当然程序创建时已经分好层,我们只需更改Student.cs文件即可,其它地方都不需要动。将Student.cs文件代码更改如下:
using System;
using System.IO;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace RazorPage.Data
{
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
//无参构造函数必须写,否则Create页面无法使用 [BindProperty]
public Student() { }
}
public static class Students
{
public static List<Student> Stus { get; set; } = new List<Student>();
//静态构造函数
static Students()
{ //如果存在json文件,则读取并返序列化为List<Student>对象
if (File.Exists("Students.json"))
{
string s = File.ReadAllText("Students.json");
Stus = JsonConvert.DeserializeObject<List<Student>>(s);
}
}
//添加一个学生
public static void Add(Student stu)
{
Stus.Add(stu);
//将Stus序列化为json文件,以进行持久保存
string s = JsonConvert.SerializeObject(Stus);
File.WriteAllText("Students.json", s);
}
public static Student GetById(int id)
{
for (int i = 0; i < Stus.Count; i++)
{
if (Stus[i].Id == id)
{
return Stus[i];
}
}
return null;
}
//删除一个学生,删除成功返回元素位置索引,失败返回-1
public static int Delete(int id)
{
for (int i = 0; i < Stus.Count; i++)
{
if (Stus[i].Id == id)
{
Stus.RemoveAt(i);
string s = JsonConvert.SerializeObject(Stus);
File.WriteAllText("Students.json", s);
return i;
}
}
return -1;
}
//更改指定Id的学生的姓名,成功返回修改元素位置索引,失败返回-1
public static int Update(int id, string name)
{
for (int i = 0; i < Stus.Count; i++)
{
if (Stus[i].Id == id)
{
Stus[i].Name = name;
string s = JsonConvert.SerializeObject(Stus);
File.WriteAllText("Students.json", s);
return i;
}
}
return -1;
}
}
}
运行程序,然后执行各种操作,关闭程序,再打开,之前的数据已经保存下来。
好,程序写完,现在可以去看创建一个web应用程序这篇文章了。
;